#region Usings
using System;
using System.Collections.Generic;
using System.IO;
using System.Collections.Specialized;
using System.Text;
using System.Net;
using System.Web;
using System.Globalization;
using System.Text.RegularExpressions;
using System.Collections;
using RapidSpider.Enumerators;
using System.Threading;
using System.Diagnostics;
using RapidSpider.Views;
using RapidSpider.AccountManagementAgents;
#endregion

namespace RapidSpider.DownloadAgents
{
    public class HotfileComDownloadAgent : IDownloadAgent
    {
        #region Members
        private Uri _SourceUri;
        private decimal _SourceFileSize;
        private string _SourceServer = String.Empty;
        private string _Login = String.Empty;
        private string _Password = String.Empty;
        private string _IPAddress = String.Empty;
        private DirectoryInfo _TargetDirectory;
        private bool _AllowOverwrite;
        private int _DownloadBufferSize = 8192; // 8KB
        private int _Timeout = 10000; // milliseconds
        private DownloadAgentStatus _Status = DownloadAgentStatus.NotStarted;
        private string _StatusMessage = String.Empty;
        private long _DownloadedAmount;
        private long _TargetFileSize;
        private decimal _DownloadSpeed;
        private const int SPEED_SAMPLE_FREQUENCY = 8;
        #endregion

        #region Properties
        public Uri SourceUri
        {
            get { return this._SourceUri; }
        }

        public decimal SourceFileSize
        {
            get { return Math.Round(this._SourceFileSize / 1048576, 2); }
        }

        public string SourceServer
        {
            get { return this._SourceServer; }
        }

        public DownloadAgentStatus Status
        {
            get { return this._Status; }
            set 
            { 
                this._Status = value;

                if (null != this.StatusChanged)
                {
                    this.StatusChanged(this, new EventArgs());
                }
            }
        }

        public string StatusMessage
        {
            get { return this._StatusMessage; }
            set { this._StatusMessage = value; }
        }
        
        public int DownloadedPercentage
        {
            get
            {
                if (this._TargetFileSize.Equals(0))
                {
                    return 0;
                }
                return Convert.ToInt32(
                    ((decimal)this._DownloadedAmount /
                        (decimal)this._TargetFileSize) * 100,
                    CultureInfo.CurrentCulture);
            }
        }

        public decimal RemainingAmount
        {
            get
            {
                decimal theRemainingAmount = Convert.ToDecimal(
                    this._TargetFileSize - this._DownloadedAmount,
                    CultureInfo.CurrentCulture);

                return Math.Round(theRemainingAmount / 1048576, 2);
            }
        }

        public long DownloadedAmount
        {
            get { return this._DownloadedAmount; }
        }

        public decimal DownloadSpeed
        {
            get { return Math.Round(this._DownloadSpeed, 2); }
        }
        
        public FileInfo IncompleteTargetFileInfo
        {
            get
            {
                return new FileInfo(String.Format(
                    CultureInfo.CurrentCulture,
                    @"{0}\{1}.incomplete",
                    this._TargetDirectory,
                    this._SourceUri.Segments[this._SourceUri.Segments.Length - 1]));
            }
        }

        public FileInfo TargetFileInfo
        {
            get
            {
                return new FileInfo(String.Format(
                    CultureInfo.CurrentCulture,
                    @"{0}\{1}",
                    this._TargetDirectory,
                    this._SourceUri.Segments[this._SourceUri.Segments.Length - 1]));
            }
        }
        
        public long TargetFileSize
        {
            get { return this._TargetFileSize; }
        }
        #endregion

        #region Methods
        #region Constructor
        public HotfileComDownloadAgent(
            Uri theSourceURI, 
            long theDownloadedAmount,
            DirectoryInfo theTargetDirectory,
            string theLogin,
            string thePassword,
            bool AllowOvewrite,
            int theDownloadBufferSize,
            int theTimeout)
        {
            if (null == theSourceURI)
            {
                throw new ArgumentException("Source URI not supplied!");
            }
            if (null == theTargetDirectory || !theTargetDirectory.Exists)
            {
                throw new ArgumentException("Target Directory not supplied!");
            }
            if (String.IsNullOrEmpty(theLogin))
            {
                throw new ArgumentException("Premium Account Login not supplied!");
            }
            if (String.IsNullOrEmpty(thePassword))
            {
                throw new ArgumentException("Premium Account Password not supplied!");
            } 
            
            StringBuilder theURL = new StringBuilder();
            theURL.Append(@"http://rs");
            theURL.Append(new Random().Next(1, 100).ToString(CultureInfo.CurrentCulture));
            theURL.Append(".");
            theURL.Append(theSourceURI.ToString().Substring(7));

            this._SourceUri = theSourceURI;
            this._TargetDirectory = theTargetDirectory;
            this._DownloadedAmount = theDownloadedAmount;
            this._Login = theLogin;
            this._Password = thePassword;
            this._AllowOverwrite = AllowOvewrite;
            this._DownloadBufferSize = theDownloadBufferSize;
            this._Timeout = theTimeout;
        }
        #endregion

        #region Initialise
        public void Initialise()
        {
            if (null == this._SourceUri)
            {
                throw new ArgumentException("Source URI not supplied!");
            }
            if (null == this._TargetDirectory)
            {
                throw new ArgumentException("Target Directory not supplied.");
            }
            if (String.IsNullOrEmpty(this._Login))
            {
                throw new ArgumentException("Premium Account Login not supplied!");
            }
            if (String.IsNullOrEmpty(this._Password))
            {
                throw new ArgumentException("Premium Account Password not supplied!");
            }

            this.Status = DownloadAgentStatus.Initialising;
            if (null != this.Initialising)
            {
                this.Initialising(this, new EventArgs());
            }

            try
            {
                if (!this.TargetFileInfo.Exists || this._AllowOverwrite)
                {
                    #region Set the actual file URI
                    StringBuilder thePostData = new StringBuilder();
                    thePostData.AppendFormat(CultureInfo.CurrentCulture, "returnto={0}", this._SourceUri.ToString().Replace("http://hotfile.com", ""));
                    thePostData.AppendFormat(CultureInfo.CurrentCulture, "&user={0}", this._Login);
                    thePostData.AppendFormat(CultureInfo.CurrentCulture, "&pass={0}", this._Password);

                    byte[] theEncodedData = new ASCIIEncoding().GetBytes(thePostData.ToString());

                    HttpWebRequest theRequest = (HttpWebRequest)WebRequest.Create("http://www.hotfile.com/login.php");
                    theRequest.Method = "POST";
                    theRequest.CookieContainer = new CookieContainer();
                    theRequest.ContentType = "application/x-www-form-urlencoded";
                    theRequest.ContentLength = theEncodedData.Length;
                    theRequest.Timeout = this._Timeout;

                    using (Stream theRequestStream = theRequest.GetRequestStream())
                    {
                        theRequestStream.Write(theEncodedData, 0, theEncodedData.Length);
                    }

                    using (HttpWebResponse theResponse = (HttpWebResponse)theRequest.GetResponse())
                    {
                        using (Stream theResponseStream = theResponse.GetResponseStream())
                        {
                            using (StreamReader theStreamReader = new StreamReader(theResponseStream))
                            {
                                string thePage = theStreamReader.ReadToEnd();

                                if (String.IsNullOrEmpty(thePage))
                                {
                                    throw new WebException("File not found", WebExceptionStatus.SendFailure);
                                }

                                Regex theRegularExpression = new Regex(
                                    "(http://(s[^\\.]*.hotfile.com)/get/[^/]*/[^/]*/[^\\.\\s]*\\..[^<\"\'\\s&]*)",
                                    RegexOptions.IgnoreCase);

                                Match theURI = theRegularExpression.Match(thePage);

                                if (theURI.Success)
                                {
                                    this._SourceUri = new Uri(theURI.Groups[1].Value);
                                    this._SourceServer = theURI.Groups[2].Value;
                                    this._IPAddress = theResponse.Cookies[0].Value;
                                }
                            }
                        }
                    }
                    #endregion

                    #region Get the file size
                    theRequest = (HttpWebRequest)WebRequest.Create(this._SourceUri);
                    theRequest.Headers.Add("Cookie",
                        String.Format(
                            CultureInfo.CurrentCulture,
                            "ip={0}",
                            this._IPAddress));
                    theRequest.Method = "GET";
                    theRequest.Referer = this._SourceUri.ToString();
                    theRequest.Timeout = this._Timeout;

                    using (HttpWebResponse theResponse = (HttpWebResponse)theRequest.GetResponse())
                    {
                        this._SourceFileSize =
                            theResponse.ContentLength;
                    }
                    #endregion
                    
                    this.Status = DownloadAgentStatus.Initialised;
                    if (null != this.Initialised)
                    {
                        this.Initialised(this, new EventArgs());
                    }
                }
            }
            catch (WebException theWebException)
            {
                if (theWebException.Status.Equals(WebExceptionStatus.Timeout))
                {
                    this.Status = DownloadAgentStatus.TimedOut;
                }
                else if (theWebException.Status.Equals(WebExceptionStatus.SendFailure) &&
                    theWebException.Message.Equals("This file has been deleted"))
                {
                    this.Status = DownloadAgentStatus.FileDeleted;
                }
                else if (theWebException.Status.Equals(WebExceptionStatus.SendFailure) &&
                    theWebException.Message.Equals("File not found"))
                {
                    this.Status = DownloadAgentStatus.FileNotFound;
                }
                else if (theWebException.Status.Equals(WebExceptionStatus.SendFailure) &&
                   theWebException.Message.Equals("This account is expired"))
                {
                    this.Status = DownloadAgentStatus.PremiumAccountExpired;
                }
                else if (theWebException.Status.Equals(WebExceptionStatus.SendFailure) &&
                   theWebException.Message.Equals("momentarily not available"))
                {
                    this.Status = DownloadAgentStatus.ServerUnavailable;
                }
                else
                {
                    this.StatusMessage = theWebException.Message;
                    this.Status = DownloadAgentStatus.Failed;
                }
                if (null != this.Initialised)
                {
                    this.Initialised(this, new EventArgs());
                }
            }
            catch (Exception theException)
            {
                if (!theException.GetType().Equals(typeof(ThreadAbortException)))
                {
                    this._StatusMessage = theException.Message;
                    this.Status = DownloadAgentStatus.Failed;
                    if (null != this.Initialised)
                    {
                        this.Initialised(this, new EventArgs());
                    }
                }
            }
        }
        #endregion

        #region Start
        /// <summary>
        /// Starts the download of the attached url into the given directory.
        /// </summary>
        public void Start()
        {
            try
            {
                if (!this.TargetFileInfo.Exists || this._AllowOverwrite)
                {
                    this.Status = DownloadAgentStatus.Starting;
                    if (null != this.Starting)
                    {
                        this.Starting(this, new EventArgs());
                    }
                    
                    #region Download File
                    HttpWebRequest theRequest = (HttpWebRequest)WebRequest.Create(this._SourceUri);
                    theRequest.AddRange(
                        Convert.ToInt32(
                            this._DownloadedAmount,
                            CultureInfo.CurrentCulture));
                    theRequest.Headers.Add("Cookie",
                        String.Format(
                            CultureInfo.CurrentCulture,
                            "ip={0}",
                            this._IPAddress));
                    theRequest.Method = "GET";
                    theRequest.Referer = this._SourceUri.ToString();
                    theRequest.Timeout = this._Timeout;

                    using (HttpWebResponse theResponse = (HttpWebResponse)theRequest.GetResponse())
                    {
                        using (Stream theStream = theResponse.GetResponseStream())
                        {
                            DirectoryInfo theDirectoryInfo =
                                new DirectoryInfo(this.TargetFileInfo.DirectoryName);
                            if (!theDirectoryInfo.Exists)
                            {
                                theDirectoryInfo.Create();
                            }

                            using (FileStream theFileStream = new FileStream(
                                this.IncompleteTargetFileInfo.FullName, 
                                FileMode.Append, 
                                FileAccess.Write))
                            {
                                if (this._DownloadedAmount.Equals(0))
                                {
                                    this._SourceFileSize =
                                        theResponse.ContentLength;
                                }

                                this._TargetFileSize =
                                    this._DownloadedAmount + theResponse.ContentLength;

                                int theReadCount;

                                byte[] theBuffer = new byte[this._DownloadBufferSize];

                                Stopwatch thisStopWatch = new Stopwatch();
                                long theLastReadAmount = this._DownloadedAmount;
                                thisStopWatch.Start();

                                theReadCount = theStream.Read(theBuffer, 0, this._DownloadBufferSize);
                                this._DownloadedAmount += theReadCount;

                                while (theReadCount > 0)
                                {
                                    if (this.Status.Equals(DownloadAgentStatus.Paused))
                                    {
                                        if (null != this.Paused)
                                        {
                                            this.Paused(this, new EventArgs());
                                        }
                                        return;
                                    }
                                    else if (this.Status.Equals(DownloadAgentStatus.Cancelled))
                                    {
                                        if (null != this.Cancelled)
                                        {
                                            this.Cancelled(this, new EventArgs());
                                        }
                                        return;
                                    }
                                    else
                                    {
                                        this.Status = DownloadAgentStatus.Downloading;
                                        if (null != this.Downloading)
                                        {
                                            this.Downloading(this, new EventArgs());
                                        }

                                        theFileStream.Write(theBuffer, 0, theReadCount);

                                        theReadCount = theStream.Read(theBuffer, 0, this._DownloadBufferSize);

                                        if (thisStopWatch.ElapsedMilliseconds >= 200)
                                        {
                                            decimal theSecondCount = (decimal)thisStopWatch.ElapsedMilliseconds / (decimal)1000;
                                            decimal theKilobyteCount = (this._DownloadedAmount - theLastReadAmount) / (decimal)1000;

                                            this._DownloadSpeed =
                                                theKilobyteCount / theSecondCount;

                                            thisStopWatch.Reset();
                                            theLastReadAmount = this._DownloadedAmount;
                                            thisStopWatch.Start();
                                        }

                                        this._DownloadedAmount += theReadCount;
                                    }
                                }

                                thisStopWatch.Reset();
                            }
                        }
                    }
                    #endregion
                }

                File.Move(
                    this.IncompleteTargetFileInfo.FullName,
                    this.TargetFileInfo.FullName);

                this.Status = DownloadAgentStatus.Successful;
                if (null != this.Completed)
                {
                    this.Completed(this, new EventArgs());
                }
            }
            catch (WebException theWebException)
            {
                if (theWebException.Status.Equals(WebExceptionStatus.Timeout))
                {
                    this.Status = DownloadAgentStatus.TimedOut;
                }
                else if (theWebException.Status.Equals(WebExceptionStatus.ProtocolError) &&
                    theWebException.Message.Contains("403"))
                {
                    this.Status = DownloadAgentStatus.LimitExceeded;
                }
                else if (theWebException.Status.Equals(WebExceptionStatus.SendFailure) &&
                    theWebException.Message.Equals("This file has been deleted"))
                {
                    this.Status = DownloadAgentStatus.FileDeleted;
                }
                else if (theWebException.Status.Equals(WebExceptionStatus.SendFailure) &&
                    theWebException.Message.Equals("File not found"))
                {
                    this.Status = DownloadAgentStatus.FileNotFound;
                }
                else if (theWebException.Status.Equals(WebExceptionStatus.SendFailure) &&
                   theWebException.Message.Equals("This account is expired"))
                {
                    this.Status = DownloadAgentStatus.PremiumAccountExpired;
                }
                else
                {
                    this.StatusMessage = theWebException.Message;
                    this.Status = DownloadAgentStatus.Failed;
                }
                if (null != this.Failed)
                {
                    this.Failed(this, new EventArgs());
                }
            }
            catch (Exception theException)
            {
                if (!theException.GetType().Equals(typeof(ThreadAbortException)))
                {
                    this._StatusMessage = theException.Message;
                    this.Status = DownloadAgentStatus.Failed;
                    if (null != this.Failed)
                    {
                        this.Failed(this, new EventArgs());
                    }
                }
            }
        }
        #endregion

        #region Private Helpers
        private static string URLEncode(string theString)
        {
            StringBuilder theData = new StringBuilder();

            foreach (char thisChar in theString.ToCharArray())
            {
                theData.Append("%");
                theData.AppendFormat(
                    CultureInfo.CurrentCulture,
                    "{0:X2}",
                    (int)thisChar);
            }

            return theData.ToString();
        }
        #endregion
        #endregion

        #region Events
        public event EventHandler Initialising;
        public event EventHandler Initialised;
        public event EventHandler Starting;
        public event EventHandler Downloading;
        public event EventHandler Paused;
        public event EventHandler Completed;
        public event EventHandler Cancelled;
        public event EventHandler Failed;
        public event EventHandler StatusChanged;
        #endregion
    }
}
